package com.open.rbac.service.sys.impl;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.open.center.common.exception.OpenException;
import com.open.center.common.util.CopyUtils;
import com.open.center.jpa.util.SpecificationUtils;
import com.open.rbac.config.security.MeUsernamePasswordAuthenticationToken;
import com.open.rbac.constant.ImmutableRole;
import com.open.rbac.constant.ImmutableUser;
import com.open.rbac.constant.AdminConstant;
import com.open.rbac.model.dto.sys.OrganizationDto;
import com.open.rbac.model.dto.sys.RoleDto;
import com.open.rbac.model.dto.sys.UserDto;
import com.open.rbac.model.entity.sys.User;
import com.open.rbac.model.query.UserQuery;
import com.open.rbac.model.vo.sys.OrganizationUserRelVo;
import com.open.rbac.model.vo.sys.RoleUserRelVo;
import com.open.rbac.model.vo.sys.UserVo;
import com.open.rbac.service.sys.IOrganizationService;
import com.open.rbac.service.sys.IOrganizationUserRelService;
import com.open.rbac.service.sys.IRoleService;
import com.open.rbac.service.sys.IRoleUserRelService;
import com.open.rbac.service.sys.IUserService;
import com.open.rbac.service.sys.repository.UserRepository;


/**
 * 用户服务
 *
 * @author Riche's
 * @since 2022/8/12
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class UserServiceImpl implements IUserService {

    private final PasswordEncoder passwordEncoder;

    private final UserRepository userRepository;


    private final IRoleService roleService;

    private final IRoleUserRelService roleUserRelService;

    private final IOrganizationService organizationService;

    private final IOrganizationUserRelService organizationUserRelService;


    @Transactional
    @Override
    public UserDto create(UserVo userVo) {
        synchronized (userRepository) {
            for (ImmutableUser mu : ImmutableUser.values()) {
                if (mu.username().equalsIgnoreCase(userVo.getUsername())) {
                    log.error("[固定用户]用户名：{} 已存在", userVo.getUsername());
                    throw new OpenException("用户名已存在");
                }
            }

            User nameUser = userRepository.findByUsername(userVo.getUsername());
            if (nameUser != null) {
                log.error("用户名：{} 已存在", userVo.getUsername());
                throw new OpenException("用户名已存在");
            }

            User phoneUser = userRepository.findByPhone(userVo.getPhone());
            if (phoneUser != null) {
                log.error("电话号码：{} 已存在", userVo.getPhone());
                throw new OpenException("电话号码已存在");
            }

            if (StringUtils.hasText(userVo.getEmail())) {
                List<User> emailUsers = userRepository.findByEmail(userVo.getEmail());
                if (!CollectionUtils.isEmpty(emailUsers)) {
                    log.error("邮箱：{} 已存在", userVo.getEmail());
                    throw new OpenException("邮箱已存在");
                }
            }

            User user = CopyUtils.copy(userVo, User.class);
            if (!StringUtils.hasText(user.getPassword())) {
                userVo.setPassword(new String(Base64.getDecoder().decode(AdminConstant.DEFAULT_PASSWORD), StandardCharsets.UTF_8));
            }
            user.setPassword(passwordEncoder.encode(userVo.getPassword()));
            userRepository.save(user);
            createOrganizationRels(user.getId(), userVo.getOrganizationIds());
            createRoleRels(user.getId(), userVo.getRoleIds());
            return CopyUtils.copy(user, UserDto.class);
        }
    }

    @Transactional
    @Override
    public UserDto update(UserVo userVo) {
        // 用户名不能修改
        userVo.setUsername(null);
        Optional<User> optional = userRepository.findById(userVo.getId());
        return optional.map(u -> {
            User phoneUser = userRepository.findByPhone(u.getPhone());
            if (phoneUser != null && !phoneUser.getId().equals(userVo.getId())) {
                log.error("[更新用户]电话号码：{} 已存在", userVo.getPhone());
                throw new OpenException("电话号码已存在");
            }
            if (StringUtils.hasText(userVo.getEmail())) {
                List<User> emailUsers = userRepository.findByEmail(userVo.getEmail());
                if (!CollectionUtils.isEmpty(emailUsers)
                    && emailUsers.stream().noneMatch(eu -> eu.getId().equals(userVo.getId()))) {
                    log.error("[更新用户]邮箱：{} 已存在", userVo.getEmail());
                    throw new OpenException("邮箱已存在");
                }
            }
            if (StringUtils.hasText(userVo.getPassword())) {
                userVo.setPassword(passwordEncoder.encode(userVo.getPassword()));
            }
            CopyUtils.copyNonNull(userVo, u);
            u.setRemark(userVo.getRemark());
            updateOrganizationRels(u.getId(), userVo.getOrganizationIds());
            updateRoleRels(u.getId(), userVo.getRoleIds());
            userRepository.save(u);
            return CopyUtils.copy(u, UserDto.class);
        }).orElse(null);
    }

    @Transactional
    @Override
    public void deleteById(String id) {
        MeUsernamePasswordAuthenticationToken token = verify();
        if (!(token.getRoleIds().contains(ImmutableRole.SUPER.value())
            || token.getRoleIds().contains(ImmutableRole.ADMINISTRATOR.value()))) {
            log.error("只有超级管理员/管理员才可删除用户，当前用户 {} 的角色为：{}", token.getUsername(), token.getRoleIds());
            throw new OpenException("只有超级管理员才可删除用户");
        }
        if (token.getId().equals(id)) {
            log.error("用户无法删除自己：{}", token.getUsername());
            throw new OpenException("不能删除自己");
        }
        roleUserRelService.deleteReallyByUserId(id);
        organizationUserRelService.deleteReallyByUserId(id);
        userRepository.deleteById(id);
    }

    @Override
    public void enable(List<String> ids) {
        MeUsernamePasswordAuthenticationToken token = verify();
        if (ids.contains(token.getId())) {
            log.error("不能锁定自己，当前用户 {} 的角色为：{}", token.getUsername(), token.getRoleIds());
            throw new OpenException("不能锁定自己");
        }
        for (String roleId : token.getRoleIds()) {
            if (!(roleId.equals(ImmutableRole.SUPER.value())
                || roleId.equals(ImmutableRole.ADMINISTRATOR.value()))) {
                log.error("只有{} 或 {} 才有锁定用户的权限，当前用户 {} 的角色为：{}",
                    ImmutableRole.SUPER.value(), ImmutableRole.ADMINISTRATOR.value(),
                    token.getUsername(), token.getRoleIds());
                throw new OpenException("没有锁定用户的权限");
            }
        }
        userRepository.updateEnable(ids, true);
    }

    @Override
    public void disable(List<String> ids) {
        MeUsernamePasswordAuthenticationToken token = verify();
        for (String roleId : token.getRoleIds()) {
            if (!(roleId.equals(ImmutableRole.SUPER.value())
                || roleId.equals(ImmutableRole.ADMINISTRATOR.value()))) {
                log.error("只有{} 或 {} 才有解锁用户的权限，当前用户 {} 的角色为：{}",
                    ImmutableRole.SUPER.value(), ImmutableRole.ADMINISTRATOR.value(),
                    token.getUsername(), token.getRoleIds());
                throw new OpenException("没有解锁用户的权限");
            }
        }
        userRepository.updateEnable(ids, false);
    }

    protected MeUsernamePasswordAuthenticationToken verify() {
        MeUsernamePasswordAuthenticationToken token = (MeUsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        if (token == null) {
            log.error("无法从Security上下文中获取Token信息");
            throw new OpenException("无法从Security上下文中获取Token信息");
        } else if (CollectionUtils.isEmpty(token.getRoleIds())) {
            log.error("没有找到用户：{} 的Token中的角色信息", token.getUsername());
            throw new OpenException("无法从Token中提取角色信息");
        }
        return token;
    }

    @Override
    public void resetPassword(String id) {
        userRepository.updatePassword(id, passwordEncoder.encode(new String(Base64.getDecoder()
            .decode(AdminConstant.DEFAULT_PASSWORD), StandardCharsets.UTF_8)));
    }

    @Override
    public void updatePassword(String id, String password) {
        userRepository.updatePassword(id, passwordEncoder.encode(password));
    }

    @Override
    public void updateAvatar(String id, String avatar) {
        userRepository.updateAvatar(id, avatar);
    }

    @Override
    public void createOrganizations(String userId, List<String> orgIds) {
        updateOrganizationRels(userId, orgIds);
    }

    @Transactional
    @Override
    public void createRoles(String userId, List<String> roleIds) {
        updateRoleRels(userId, roleIds);
    }

    protected void updateOrganizationRels(final String userId, List<String> organizationIds) {
        organizationUserRelService.deleteReallyByUserId(userId);
        createOrganizationRels(userId, organizationIds);
    }

    protected void updateRoleRels(final String userId, List<String> roleIds) {
        roleUserRelService.deleteReallyByUserId(userId);
        createRoleRels(userId, roleIds);
    }

    private void createOrganizationRels(final String userId, List<String> organizationIds) {
        List<OrganizationUserRelVo> relVos = organizationIds.stream()
            .map(organizationId -> OrganizationUserRelVo.of(organizationId, userId)).toList();
        organizationUserRelService.create(relVos);
    }

    private void createRoleRels(final String userId, List<String> roleIds) {
        List<RoleUserRelVo> relVos = roleIds.stream().map(roleId -> RoleUserRelVo.of(roleId, userId)).toList();
        roleUserRelService.create(relVos);
    }

    @Override
    public UserDto findById(String id) {
        Optional<User> optional = userRepository.findById(id);
        return optional.map(this::copyUserDto).orElse(null);
    }

    @Override
    public UserDto findByUsername(String username) {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            return null;
        }
        return copyUserDto(user);
    }

    @Override
    public List<UserDto> list() {
        List<User> users = userRepository.findAllByEnable(true);
        if (CollectionUtils.isEmpty(users)) {
            return Collections.emptyList();
        }
        return users.stream().filter(u ->
                !(ImmutableRole.ADMINISTRATOR.value().equals(u.getId())
                    || ImmutableRole.SUPER.value().equals(u.getId())))
            .sorted(Comparator.comparing(User::getUpdateTime).reversed())
            .map(this::copyUserDto).toList();
    }

    @Override
    public Page<UserDto> page(UserQuery query) {
        Page<User> page = userRepository.findAll(putSpecification(query), query.getRequest());
        return new PageImpl<>(copyUserDtos(page.getContent()),
            page.getPageable(), page.getTotalElements());
    }

    private Specification<User> putSpecification(UserQuery query) {
        Specification<User> specification = SpecificationUtils.specification(query);
        if (StringUtils.hasText(query.getOrganizationId())) {
            List<String> userIds = new ArrayList<>();
            List<String> organizationIds = organizationService.listChildIdsById(query.getOrganizationId());
            if (!CollectionUtils.isEmpty(organizationIds)) {
                userIds.addAll(organizationUserRelService.listUserIds(organizationIds));
            }
            if (CollectionUtils.isEmpty(userIds)) {
                userIds.add("");
            }
            Specification<User> inSpecification = (root, q, cb) -> {
                List<Predicate> predicates = new ArrayList<>();
                CriteriaBuilder.In<String> in = cb.in(root.get("id"));
                for (String userId : userIds) {
                    in.value(userId);
                }
                predicates.add(in);
                Predicate[] p = new Predicate[0];
                return cb.and(predicates.toArray(p));
            };
            specification = specification.and(inSpecification);
        }
        return specification;
    }


    public UserDto copyUserDto(User user) {
        UserDto userDto = CopyUtils.copy(user, UserDto.class);
        userDto.setShowEmail(user.getEmail());
        userDto.setShowPhone(user.getPhone());
        Map<String, List<RoleDto>> mapRoleDtos = roleService.mapByUserIds(List.of(user.getId()));
        Map<String, List<OrganizationDto>> mapOrganizationDtos = organizationService.mapByUserIds(List.of(user.getId()));
        userDto.setRoles(mapRoleDtos.get(user.getId()));
        userDto.setOrganizations(mapOrganizationDtos.get(user.getId()));
        return userDto;
    }

    public List<UserDto> copyUserDtos(List<User> users) {
        if (CollectionUtils.isEmpty(users)) {
            return Collections.emptyList();
        }
        List<String> userIds = users.stream().map(User::getId).toList();
        Map<String, List<RoleDto>> mapRoleDtos = roleService.mapByUserIds(userIds);
        Map<String, List<OrganizationDto>> mapOrganizationDtos = organizationService.mapByUserIds(userIds);
        return users.stream().map(user -> {
            UserDto userDto = CopyUtils.copy(user, UserDto.class);
            userDto.setShowEmail(user.getEmail());
            userDto.setShowPhone(user.getPhone());
            userDto.setRoles(mapRoleDtos.get(user.getId()));
            userDto.setOrganizations(mapOrganizationDtos.get(user.getId()));
            if (!CollectionUtils.isEmpty(userDto.getRoles())) {
                userDto.setRoleText(String.join(",",
                    userDto.getRoles().stream().map(RoleDto::getName).toList()));
            }
            if (!CollectionUtils.isEmpty(userDto.getOrganizations())) {
                userDto.setOrganizationText(String.join(",",
                    userDto.getOrganizations().stream().map(OrganizationDto::getName).toList()));
            }
            return userDto;
        }).toList();
    }

}
